import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { DefaultStage } from '../default-stage';
import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { UAV } from './uav';
import { AbstractMesh, AnimationFrame } from 'babylon-lib/index';
import { Landscape } from './landscape';
import { ParticleSystem } from '@babylonjs/core/Particles/particleSystem';
import { RawAssetsUrl } from '../../raw-assets-info';
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
import { Color3, Color4 } from '@babylonjs/core/Maths/math.color';
import { Random } from 'babylon-lib/tool/random';
import { ExplodeImprint } from './explode-imprint';
import { RenderOrder } from 'babylon-lib/misc/render-order';
import * as Data from '../../data/data';
import { Helper } from 'babylon-lib/tool/helper';
import { SoundManager } from './sound-manager';


interface IBombOptions {
    name: string,
    model: TransformNode,
    mount: TransformNode,
}


class Bomb {

    private _model!: TransformNode;
    private _container!: TransformNode;
    private _mountTarget!: TransformNode;
    private _req = {};
    private _expReq = {};
    private _smkReq = {};
    private _sndReq = {};
    private _resistance = 0.999;
    private _psTrail: ParticleSystem;
    private _psSmoke: ParticleSystem;
    private _psExplode: ParticleSystem;

    public readonly name: string;
    public readonly mass = 0.5;

    constructor(stage: DefaultStage, options: IBombOptions) {
        this.name = options.name;
        this.initializeBody(stage, options);
        this._psTrail = this.initializePSTrail(stage);
        this._psExplode = this.initializePSExplode(stage);
        this._psSmoke = this.initializePSSmoke(stage);
    }

    public get entity(): TransformNode {
        return this._container;
    }

    public calculateDamage(distance: number): number {
        if (distance > 900) return 0;
        if (distance > 400) return Random.float(0, 0.2);
        if (distance > 100) return Random.float(0.1, 0.4);
        if (distance > 25) return Random.float(0.3, 0.8);
        if (distance > 4) return Random.float(0.6, 1.4);
        return Random.float(0.9, 2);
    }

    public mount(mount: boolean): void {
        if (mount) {
            this._container.setParent(this._mountTarget);
            this._container.position.set(0, 0, 0);
            this._container.scaling.set(1, 1, 1);
            this._container.rotationQuaternion = Quaternion.Identity();
            Helper.getChildMeshes(this._model).forEach(m => m.isVisible = true);
            AnimationFrame.cancel(this._req);
            AnimationFrame.cancel(this._expReq);
            AnimationFrame.cancel(this._smkReq);
            this._psTrail.stop();
            this._psSmoke.stop();
            this._psExplode.stop();
            this._psTrail.reset();
            this._psSmoke.reset();
            this._psExplode.reset();
        } else {
            this._container.setParent(null);
            this.drop();
        }
    }

    private initializePSTrail(stage: DefaultStage): ParticleSystem {
        const duration = 2;
        const count = 40;
        const power = 0.01;
        const color = Color3.FromHexString('#cecece');
        const size = 0.5;
        const scn = stage.mainScene;
        const texture = stage.loader(scn).getTextureTaskInfo(RawAssetsUrl.textureYanwu)?.texture!;

        const ps = new ParticleSystem(this.name + '-trail-particle-system', count, scn);
        ps.particleTexture = texture;

        const m = MeshBuilder.CreateBox(this.name + '-trail-particales-emmiter', { size: 0.1 }, scn);
        m.visibility = 0;
        ps.emitter = m;
        ps.minInitialRotation = 0;
        ps.maxInitialRotation = 6.28;
        ps.minLifeTime = duration * 0.5;
        ps.maxLifeTime = duration;
        ps.emitRate = count * 0.5;
        ps.createPointEmitter(new Vector3(0, 1, 0), new Vector3(0, 1, 0));
        ps.minEmitPower = power;
        ps.maxEmitPower = power * 1.5;
        ps.updateSpeed = 0.02;
        ps.gravity = new Vector3(0.1, 0, 0);
        m.parent = this._container;
        m.position.set(0, 0.1, 0);

        ps.addColorGradient(0, new Color4(color.r, color.g, color.b, 0));
        ps.addColorGradient(0.1, new Color4(color.r, color.g, color.b, 0.2));
        ps.addColorGradient(1, new Color4(color.r, color.g, color.b, 0));

        ps.addSizeGradient(0, size * 0.02, size * 0.04);
        ps.addSizeGradient(0.3, size * 0.5, size * 0.7);
        ps.addSizeGradient(1, size * 0.7, size);

        ps.blendMode = ParticleSystem.BLENDMODE_STANDARD;
        ps.renderingGroupId = RenderOrder.id(Data.orders.scene);
        return ps;
    }

    private initializePSSmoke(stage: DefaultStage): ParticleSystem {
        const duration = 20;
        const count = 200;
        const power = 0.1;
        const color = Color3.FromHexString('#cecece');
        const size = 1.5;
        const scn = stage.mainScene;
        const texture = stage.loader(scn).getTextureTaskInfo(RawAssetsUrl.textureYanwu)?.texture!;

        const ps = new ParticleSystem(this.name + '-explode-particle-system', count, scn);
        ps.particleTexture = texture;

        const m = MeshBuilder.CreateBox(this.name + '-explode-particales-emmiter', { size: 0.1 }, scn);
        m.visibility = 0;
        ps.emitter = m;
        ps.minInitialRotation = 0;
        ps.maxInitialRotation = 6.28;
        ps.minLifeTime = duration * 0.5;
        ps.maxLifeTime = duration;
        ps.minAngularSpeed = -0.2;
        ps.maxAngularSpeed = 0.2;

        ps.emitRate = count * 5;
        ps.createSphereEmitter(1.3, 1);
        ps.minEmitPower = 0;
        ps.maxEmitPower = power;
        ps.updateSpeed = 0.02;
        ps.gravity = new Vector3(1, 0.1, 0);

        ps.addColorGradient(0, new Color4(color.r, color.g, color.b, 0));
        ps.addColorGradient(0.02, new Color4(color.r, color.g, color.b, 0.6));
        ps.addColorGradient(1, new Color4(color.r, color.g, color.b, 0));

        ps.addSizeGradient(0, size * 0.1, size);
        ps.addSizeGradient(1, size * 0.2, size * 2);

        ps.addVelocityGradient(0, 0, 1);

        ps.blendMode = ParticleSystem.BLENDMODE_STANDARD;
        ps.renderingGroupId = RenderOrder.id(Data.orders.scene);
        return ps;
    }

    private initializePSExplode(stage: DefaultStage): ParticleSystem {
        const duration = 0.1;
        const count = 20;
        const power = 0;
        const color = Color3.FromHexString('#ffa600');
        const size = 3;
        const scn = stage.mainScene;
        const texture = stage.loader(scn).getTextureTaskInfo(RawAssetsUrl.textureYanwu)?.texture!;

        const ps = new ParticleSystem(this.name + '-explode-particle-system', count, scn);
        ps.particleTexture = texture;

        const m = MeshBuilder.CreateBox(this.name + '-explode-particales-emmiter', { size: 0.1 }, scn);
        m.visibility = 0;
        ps.emitter = m;
        ps.minInitialRotation = 0;
        ps.maxInitialRotation = 6.28;
        ps.minLifeTime = duration;
        ps.maxLifeTime = duration;
        ps.emitRate = count * 5;
        ps.createPointEmitter(new Vector3(0, 1, 0), new Vector3(0, 1, 0));
        ps.minEmitPower = power;
        ps.maxEmitPower = power * 1.5;
        ps.updateSpeed = 0.02;
        ps.gravity = new Vector3(0, 1, 0);

        ps.addColorGradient(0, new Color4(color.r, color.g, color.b, 0));
        ps.addColorGradient(1, new Color4(color.r, color.g, color.b, 1));

        ps.addSizeGradient(0, size * 0.4, size * 0.8);
        ps.addSizeGradient(1, size * 0.5, size);

        ps.blendMode = ParticleSystem.BLENDMODE_ADD;
        ps.renderingGroupId = RenderOrder.id(Data.orders.scene);
        return ps;
    }

    private drop(): void {
        AnimationFrame.cancel(this._req);
        const dt = Landscape.delta;
        const m = this.mass;
        let grvA = Landscape.gravity / m;
        let grvS = 0;

        let wndA = Landscape.wind.length() / m;
        const wndD = new Vector3().copyFrom(Landscape.wind).normalize();
        let wndS = 0;
        const wnd = new Vector3();

        const itaF = new Vector3().copyFrom(UAV.inertia);
        const grv = new Vector3().copyFrom(Landscape.wind);
        grv.y = 0.05;
        this._psTrail.gravity = grv;
        this._psExplode.gravity = grv;
        this._psSmoke.gravity = grv;

        const rst = this._resistance;
        const ofs = new Vector3();
        const ctn = this._container;
        const rot = Quaternion.FromEulerAngles(Random.float(0, 0.05), 0, Random.float(0, 0.05))
        const toh = ctn.position.y;
        const tsl = 0.4;
        const dsl = (ctn.scaling.x - tsl) / toh;
        AnimationFrame.request(() => {
            grvA *= rst;
            grvS += grvA * dt;
            const grvT = grvS * dt;
            ofs.y = -grvT;

            wndA *= rst;
            wndS += wndA * dt;
            const wndT = wndS * dt;
            wnd.copyFrom(wndD).scaleInPlace(wndT);

            itaF.scaleInPlace(rst);

            ctn.position.addInPlace(ofs).addInPlace(wnd).addInPlace(itaF);
            ctn.rotationQuaternion?.multiplyInPlace(rot);

            const s = Math.max(ctn.position.y - toh * 0.5, 0) * dsl + tsl;
            ctn.scaling.set(s, s, s);

            if (ctn.position.y < 0) {
                this._psTrail.stop();
                this.explode(true);
                return true;
            }

        }, 16, 200, this._req);

        this._psTrail.start(500);
    }

    private explode(play: boolean): void {
        if (!play) {
            this._psSmoke.stop();
            this._psExplode.stop();
            AnimationFrame.cancel(this._expReq);
            AnimationFrame.cancel(this._smkReq);
            AnimationFrame.cancel(this._sndReq);
            return;
        }
        const pos = new Vector3().copyFrom(this._container.absolutePosition);
        pos.y += 0.2;
        const pe = this._psExplode.emitter as AbstractMesh;
        pe.position.copyFrom(pos);
        const ps = this._psSmoke.emitter as AbstractMesh;
        ps.position.copyFrom(pos);

        this._psExplode.start();
        Helper.getChildMeshes(this._model).forEach(m => m.isVisible = false);
        AnimationFrame.throttleByFrame(this._expReq, 3, () => {
            this._psExplode.stop();
            this._psSmoke.start();
            ExplodeImprint.generate(pos);
            Landscape.addDangrousPoint(this.entity.absolutePosition);
            Landscape.checkDamage(this);
        }, false);

        AnimationFrame.throttleByTime(this._sndReq, 400, () => {
            SoundManager.explode.play();
        }, false);

        AnimationFrame.throttleByFrame(this._smkReq, 200, () => {
            this._psSmoke.stop();
        }, false);
    }

    private initializeBody(stage: DefaultStage, ops: IBombOptions): void {
        this._model = ops.model;
        this._model.scaling.set(1, 1, 1);

        this._container = new TransformNode(ops.name + '-container', stage.mainScene);
        this._container.rotationQuaternion = Quaternion.Identity();
        this._model.parent = this._container;
        this._model.position.set(0, 0, 0);
        this._mountTarget = ops.mount;
    }

}


export { Bomb };
export type { IBombOptions };